package com.tomorrow.weather.widget;

import com.tomorrow.weather.mInterface.IPageIndicator;
import com.tomorrow.weather.ui.R;

import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.drawable.Drawable;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.v4.view.MotionEventCompat;
import android.support.v4.view.ViewConfigurationCompat;
import android.support.v4.view.ViewPager;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;

/**
 * Draws a line for each page. The current page line is colored differently than
 * the unselected page lines.
 */
public class LinePageIndicator extends View implements IPageIndicator
{
	private static final int INVALID_POINTER = -1;
	private static final int FADE_FRAME_MS = 30;

	private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);

	private boolean mFades;
	private int mFadeDelay;
	private int mFadeLength;
	private int mFadeBy;

	private ViewPager mViewPager;
	private ViewPager.OnPageChangeListener mListener;
	private int mScrollState;
	private int mCurrentPage;
	private float mPositionOffset;

	private int mTouchSlop;
	private float mLastMotionX = -1;
	private int mActivePointerId = INVALID_POINTER;
	private boolean mIsDragging;

	private final Runnable mFadeRunnable = new Runnable()
	{
		@Override
		public void run()
		{
			if (!mFades)
				return;

			final int alpha = Math.max(mPaint.getAlpha() - mFadeBy, 0);
			mPaint.setAlpha(alpha);
			invalidate();
			if (alpha > 0)
			{
				postDelayed(this, FADE_FRAME_MS);
			}
		}
	};

	public LinePageIndicator(Context context)
	{
		this(context, null);
	}

	public LinePageIndicator(Context context, AttributeSet attrs)
	{
		this(context, attrs, R.attr.vpiUnderlinePageIndicatorStyle);
	}

	public LinePageIndicator(Context context, AttributeSet attrs, int defStyle)
	{
		super(context, attrs, defStyle);
		if (isInEditMode())
			return;

		final Resources res = getResources();

		// Load defaults from resources
		final boolean defaultFades = res
				.getBoolean(R.bool.default_underline_indicator_fades);
		final int defaultFadeDelay = res
				.getInteger(R.integer.default_underline_indicator_fade_delay);
		final int defaultFadeLength = res
				.getInteger(R.integer.default_underline_indicator_fade_length);
		final int defaultSelectedColor = res
				.getColor(R.color.default_underline_indicator_selected_color);

		// Retrieve styles attributes
		TypedArray a = context.obtainStyledAttributes(attrs,
				R.styleable.linePageIndicator, defStyle, 0);

		setFades(a
				.getBoolean(R.styleable.linePageIndicator_fades, defaultFades));
		setSelectedColor(a.getColor(
				R.styleable.linePageIndicator_selectedColor,
				defaultSelectedColor));
		setFadeDelay(a.getInteger(R.styleable.linePageIndicator_fadeDelay,
				defaultFadeDelay));
		setFadeLength(a.getInteger(R.styleable.linePageIndicator_fadeLength,
				defaultFadeLength));

		Drawable background = a
				.getDrawable(R.styleable.linePageIndicator_android_background);
		if (background != null)
		{
			setBackgroundDrawable(background);
		}

		a.recycle();

		final ViewConfiguration configuration = ViewConfiguration.get(context);
		mTouchSlop = ViewConfigurationCompat
				.getScaledPagingTouchSlop(configuration);
	}

	public boolean getFades()
	{
		return mFades;
	}

	public void setFades(boolean fades)
	{
		if (fades != mFades)
		{
			mFades = fades;
			if (fades)
			{
				post(mFadeRunnable);
			}
			else
			{
				removeCallbacks(mFadeRunnable);
				mPaint.setAlpha(0xFF);
				invalidate();
			}
		}
	}

	public int getFadeDelay()
	{
		return mFadeDelay;
	}

	public void setFadeDelay(int fadeDelay)
	{
		mFadeDelay = fadeDelay;
	}

	public int getFadeLength()
	{
		return mFadeLength;
	}

	public void setFadeLength(int fadeLength)
	{
		mFadeLength = fadeLength;
		mFadeBy = 0xFF / (mFadeLength / FADE_FRAME_MS);
	}

	public int getSelectedColor()
	{
		return mPaint.getColor();
	}

	public void setSelectedColor(int selectedColor)
	{
		mPaint.setColor(selectedColor);
		invalidate();
	}

	@Override
	protected void onDraw(Canvas canvas)
	{
		super.onDraw(canvas);

		if (mViewPager == null)
		{
			return;
		}
		final int count = mViewPager.getAdapter().getCount();
		if (count == 0)
		{
			return;
		}

		if (mCurrentPage >= count)
		{
			setCurrentItem(count - 1);
			return;
		}

		final int paddingLeft = getPaddingLeft();
		final float pageWidth = (getWidth() - paddingLeft - getPaddingRight())
				/ (1f * count);
		final float left = paddingLeft + pageWidth
				* (mCurrentPage + mPositionOffset);
		final float right = left + pageWidth;
		final float top = getPaddingTop();
		final float bottom = getHeight() - getPaddingBottom();
		canvas.drawRect(left, top, right, bottom, mPaint);
	}

	public boolean onTouchEvent(MotionEvent ev)
	{
		if (super.onTouchEvent(ev))
		{
			return true;
		}
		if ((mViewPager == null) || (mViewPager.getAdapter().getCount() == 0))
		{
			return false;
		}

		final int action = ev.getAction() & MotionEventCompat.ACTION_MASK;
		switch (action)
		{
		case MotionEvent.ACTION_DOWN:
			mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
			mLastMotionX = ev.getX();
			break;

		case MotionEvent.ACTION_MOVE:
		{
			final int activePointerIndex = MotionEventCompat.findPointerIndex(
					ev, mActivePointerId);
			final float x = MotionEventCompat.getX(ev, activePointerIndex);
			final float deltaX = x - mLastMotionX;

			if (!mIsDragging)
			{
				if (Math.abs(deltaX) > mTouchSlop)
				{
					mIsDragging = true;
				}
			}

			if (mIsDragging)
			{
				mLastMotionX = x;
				if (mViewPager.isFakeDragging() || mViewPager.beginFakeDrag())
				{
					mViewPager.fakeDragBy(deltaX);
				}
			}

			break;
		}

		case MotionEvent.ACTION_CANCEL:
		case MotionEvent.ACTION_UP:
			if (!mIsDragging)
			{
				final int count = mViewPager.getAdapter().getCount();
				final int width = getWidth();
				final float halfWidth = width / 2f;
				final float sixthWidth = width / 6f;

				if ((mCurrentPage > 0) && (ev.getX() < halfWidth - sixthWidth))
				{
					if (action != MotionEvent.ACTION_CANCEL)
					{
						mViewPager.setCurrentItem(mCurrentPage - 1);
					}
					return true;
				}
				else if ((mCurrentPage < count - 1)
						&& (ev.getX() > halfWidth + sixthWidth))
				{
					if (action != MotionEvent.ACTION_CANCEL)
					{
						mViewPager.setCurrentItem(mCurrentPage + 1);
					}
					return true;
				}
			}

			mIsDragging = false;
			mActivePointerId = INVALID_POINTER;
			if (mViewPager.isFakeDragging())
				mViewPager.endFakeDrag();
			break;

		case MotionEventCompat.ACTION_POINTER_DOWN:
		{
			final int index = MotionEventCompat.getActionIndex(ev);
			mLastMotionX = MotionEventCompat.getX(ev, index);
			mActivePointerId = MotionEventCompat.getPointerId(ev, index);
			break;
		}

		case MotionEventCompat.ACTION_POINTER_UP:
			final int pointerIndex = MotionEventCompat.getActionIndex(ev);
			final int pointerId = MotionEventCompat.getPointerId(ev,
					pointerIndex);
			if (pointerId == mActivePointerId)
			{
				final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
				mActivePointerId = MotionEventCompat.getPointerId(ev,
						newPointerIndex);
			}
			mLastMotionX = MotionEventCompat.getX(ev,
					MotionEventCompat.findPointerIndex(ev, mActivePointerId));
			break;
		}

		return true;
	}

	@Override
	public void setViewPager(ViewPager viewPager)
	{
		if (mViewPager == viewPager)
		{
			return;
		}
		if (mViewPager != null)
		{
			// Clear us from the old pager.
			mViewPager.setOnPageChangeListener(null);
		}
		if (viewPager.getAdapter() == null)
		{
			throw new IllegalStateException(
					"ViewPager does not have adapter instance.");
		}
		mViewPager = viewPager;
		mViewPager.setOnPageChangeListener(this);
		invalidate();
		post(new Runnable()
		{
			@Override
			public void run()
			{
				if (mFades)
				{
					post(mFadeRunnable);
				}
			}
		});
	}

	@Override
	public void setViewPager(ViewPager view, int initialPosition)
	{
		setViewPager(view);
		setCurrentItem(initialPosition);
	}

	@Override
	public void setCurrentItem(int item)
	{
		if (mViewPager == null)
		{
			throw new IllegalStateException("ViewPager has not been bound.");
		}
		mViewPager.setCurrentItem(item);
		mCurrentPage = item;
		invalidate();
	}

	@Override
	public void notifyDataSetChanged()
	{
		invalidate();
	}

	@Override
	public void onPageScrollStateChanged(int state)
	{
		mScrollState = state;

		if (mListener != null)
		{
			mListener.onPageScrollStateChanged(state);
		}
	}

	@Override
	public void onPageScrolled(int position, float positionOffset,
			int positionOffsetPixels)
	{
		mCurrentPage = position;
		mPositionOffset = positionOffset;
		if (mFades)
		{
			if (positionOffsetPixels > 0)
			{
				removeCallbacks(mFadeRunnable);
				mPaint.setAlpha(0xFF);
			}
			else if (mScrollState != ViewPager.SCROLL_STATE_DRAGGING)
			{
				postDelayed(mFadeRunnable, mFadeDelay);
			}
		}
		invalidate();

		if (mListener != null)
		{
			mListener.onPageScrolled(position, positionOffset,
					positionOffsetPixels);
		}
	}

	@Override
	public void onPageSelected(int position)
	{
		if (mScrollState == ViewPager.SCROLL_STATE_IDLE)
		{
			mCurrentPage = position;
			mPositionOffset = 0;
			invalidate();
			mFadeRunnable.run();
		}
		if (mListener != null)
		{
			mListener.onPageSelected(position);
		}
	}

	@Override
	public void setOnPageChangeListener(ViewPager.OnPageChangeListener listener)
	{
		mListener = listener;
	}

	@Override
	public void onRestoreInstanceState(Parcelable state)
	{
		SavedState savedState = (SavedState) state;
		super.onRestoreInstanceState(savedState.getSuperState());
		mCurrentPage = savedState.currentPage;
		requestLayout();
	}

	@Override
	public Parcelable onSaveInstanceState()
	{
		Parcelable superState = super.onSaveInstanceState();
		SavedState savedState = new SavedState(superState);
		savedState.currentPage = mCurrentPage;
		return savedState;
	}

	static class SavedState extends BaseSavedState
	{
		int currentPage;

		public SavedState(Parcelable superState)
		{
			super(superState);
		}

		private SavedState(Parcel in)
		{
			super(in);
			currentPage = in.readInt();
		}

		@Override
		public void writeToParcel(Parcel dest, int flags)
		{
			super.writeToParcel(dest, flags);
			dest.writeInt(currentPage);
		}

		@SuppressWarnings("UnusedDeclaration")
		public static final Creator<SavedState> CREATOR = new Creator<SavedState>()
		{
			@Override
			public SavedState createFromParcel(Parcel in)
			{
				return new SavedState(in);
			}

			@Override
			public SavedState[] newArray(int size)
			{
				return new SavedState[size];
			}
		};
	}
}